package com.wys.spring.sharding;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.extension.toolkit.SqlRunner;
import com.wys.api.exception.BizException;
import com.wys.api.utils.DateTimeUtils;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;


public class TimeShardingStrategy implements RangeShardingAlgorithm<String>, PreciseShardingAlgorithm<String> {

    private static final Logger log = LoggerFactory.getLogger(TimeShardingStrategy.class);

    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
        log.info("--------------------doShardingPrecise:{}", preciseShardingValue);
        LocalDateTime localDateTime = LocalDateTimeUtil.parse(preciseShardingValue.getValue(), "yyyy-MM-dd HH:mm:ss");
        String table = preciseShardingValue.getLogicTableName() + "_" + LocalDateTimeUtil.format(localDateTime, "yyyyMM") + "_0" + (localDateTime.getDayOfMonth() % 3 == 0 ? 3 : localDateTime.getDayOfMonth() % 3);
        if (collection.contains(table)) {
            return table;
        } else {
            throw new BizException("未知的的表:" + table);
        }
    }

    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<String> rangeShardingValue) {
        log.info("--------------------doShardingRange:{}", rangeShardingValue);
        String startTime = rangeShardingValue.getValueRange().lowerEndpoint();
        String endTime = rangeShardingValue.getValueRange().upperEndpoint();
        LocalDateTime start = DateTimeUtils.parse(startTime).minusDays(1);
        LocalDateTime end = DateTimeUtils.parse(endTime).minusDays(1);
        Set<String> list = new HashSet<>();
        List<TimeCount> timeCounts = buildTimeCount(getTime(start, end, Time.day), 0, "", start, Time.day, false);
        if (ObjectUtils.isNotEmpty(timeCounts)) {
            timeCounts.forEach(t -> {
                LocalDate localDateTime = LocalDateTimeUtil.parseDate(t.getTime(), "yyyy-MM-dd");
                String table = rangeShardingValue.getLogicTableName() + "_" + LocalDateTimeUtil.format(localDateTime, "yyyyMM") + "_0" + (localDateTime.getDayOfMonth() % 3 == 0 ? 3 : localDateTime.getDayOfMonth() % 3);
                if (collection.contains(table)) {
                    list.add(table);
                }
            });
        }
        if(ObjectUtils.isEmpty(list)){
            throw new BizException("未匹配到shardingJdbc分表");
        }
        return list;
    }

    /**
     * 一个基于时间段统计值的公共抽象方法
     *
     * @param time          时间长度
     * @param v             增量因子
     * @param sql           sql语句
     * @param localDateTime 开始时间
     * @param var           时间单位
     * @return
     */
    public List<TimeCount> buildTimeCount(long time, int v, String sql, LocalDateTime localDateTime, Time var, boolean between) {
        List<TimeCount> timeCounts = new ArrayList<>();
        Map<Integer, String> varMap = new HashMap<>();
        for (int i = 0; i < time; ) {
            //动态变量v做为i的增量因子
            if (v < 1) {
                i++;
            } else {
                i = i + v;
            }
            varMap.put(0, DateTimeUtils.format(localDateTime));
            TimeCount timeCount = new TimeCount();
            String varTime = "";
            switch (var) {
                case year:
                    timeCount.setTime(LocalDateTimeUtil.format(localDateTime.plusYears(i), var.getFormat()));
                    varTime = LocalDateTimeUtil.format(localDateTime.plusYears(i), "yyyy-MM-dd HH:mm:ss");
                    break;
                case month:
                    timeCount.setTime(LocalDateTimeUtil.format(localDateTime.plusMonths(i), var.getFormat()));
                    varTime = LocalDateTimeUtil.format(localDateTime.plusMonths(i), "yyyy-MM-dd HH:mm:ss");
                    break;
                case day:
                    timeCount.setTime(LocalDateTimeUtil.format(localDateTime.plusDays(i), var.getFormat()));
                    varTime = LocalDateTimeUtil.format(localDateTime.plusDays(i), "yyyy-MM-dd HH:mm:ss");
                    break;
                case hours:
                    timeCount.setTime(LocalDateTimeUtil.format(localDateTime.plusHours(i), var.getFormat()));
                    varTime = LocalDateTimeUtil.format(localDateTime.plusHours(i), "yyyy-MM-dd HH:mm:ss");
                    break;
                case minutes:
                    timeCount.setTime(LocalDateTimeUtil.format(localDateTime.plusMinutes(i), var.getFormat()));
                    varTime = LocalDateTimeUtil.format(localDateTime.plusMinutes(i), "yyyy-MM-dd HH:mm:ss");
                    break;
            }
            varMap.put(i, varTime);
            Map<String, Object> map = StringUtils.isNotBlank(sql) ? SqlRunner.db().selectOne(between ? String.format(sql, varMap.get(i - v), varTime) : String.format(sql, varTime)) : new HashMap<>();
            timeCount.setCount((ObjectUtils.isNotEmpty(map) && ObjectUtils.isNotEmpty(map.get("count"))) ? (Double) map.get("count") : 0);
            timeCounts.add(timeCount);
        }
        return timeCounts;
    }


    /**
     * 获取两个时间得插值
     *
     * @param start 开始时间
     * @param end   结束时间
     * @param time  换算单位
     * @return
     */
    public static long getTime(LocalDateTime start, LocalDateTime end, Time time) {
        long l = 0L;
        LocalDateTime startTime = LocalDateTime.of(start.getYear(), start.getMonth(), start.getDayOfMonth(), 0, 0, 0);
        switch (time) {
            case year:
                //fixme 开始时间为当天的00：00:00所以此处应该减去1天不然此处单位换算结果会一直是0 例如6号的23:59:59至7号的23:59:59此为1天
                l = LocalDateTimeUtil.between(startTime.minusDays(1), LocalDateTime.of(end.getYear(), end.getMonth(), end.getDayOfMonth(), 23, 45, 0), ChronoUnit.YEARS);
                break;
            case month:
                //fixme 由于2月份存在闰年和平年的区别，此处直接减一个月
                l = LocalDateTimeUtil.between(startTime.minusMonths(1), LocalDateTime.of(end.getYear(), end.getMonth(), end.getDayOfMonth(), 23, 45, 0), ChronoUnit.MONTHS);
                break;
            case day:
                l = LocalDateTimeUtil.between(startTime.minusDays(1), LocalDateTime.of(end.getYear(), end.getMonth(), end.getDayOfMonth(), 23, 45, 0), ChronoUnit.DAYS);
                break;
            case hours:
                l = LocalDateTimeUtil.between(startTime.minusHours(1), LocalDateTime.of(end.getYear(), end.getMonth(), end.getDayOfMonth(), 23, 45, 0), ChronoUnit.HOURS);
        }
        return l;
    }



    @Getter
    public enum Time {

        year("yyyy", "%Y"), month("yyyy-MM", "%Y-%m"), day("yyyy-MM-dd", "%Y-%m-%d"),
        hours("yyyy-MM-dd HH", "%Y-%m-%d %H"), minutes("yyyy-MM-dd HH:mm", "%Y-%m-%d %H:%i"), second("yyyy-MM-dd HH:mm:ss", "%Y-%m-%d %H:%i:%S");

        private final String format;

        private final String sqlFormat;

        Time(String format, String sqlFormat) {
            this.format = format;
            this.sqlFormat = sqlFormat;
        }

        public static Time queryTime(String name) {
            return Arrays.stream(Time.values()).filter(t -> t.name().equals(name)).findFirst().orElse(null);
        }
    }

    @Getter
    @Setter
    public static class TimeCount {
        @ApiModelProperty("时间")
        private String time;

        @ApiModelProperty("每个时间段总数")
        private Double count;
    }

}
